package tech

import (
	"slices"
	"ssp/set"
)

func (s *Solve) AlignedPairExclusion(g *Grid) (used bool, results []*Result) {
	allAls := g.getAllALS()
	cells := []*Cell{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if !cell.solved() {
				cells = append(cells, cell)
			}
		}
	}
	for _, pair := range combine(cells, 2) {
		x := pair[0]
		y := pair[1]
		impossibles := map[[2]int][]*Cell{}
		for _, als := range allAls {
			xseenCells := shareCells(g.seenCells(x), als)
			if len(xseenCells) == 0 {
				continue
			}
			yseenCells := shareCells(g.seenCells(y), als)
			if len(yseenCells) == 0 {
				continue
			}
			xseenCands := unionCellCands(xseenCells)
			for _, cell := range als {
				if !slices.Contains(xseenCells, cell) {
					xseenCands = xseenCands.Diff(cell.cands)
				}
			}
			yseenCands := unionCellCands(yseenCells)
			for _, cell := range als {
				if !slices.Contains(yseenCells, cell) {
					yseenCands = yseenCands.Diff(cell.cands)
				}
			}
			xcands := x.cands.Intersect(xseenCands)
			if xcands.IsNull() {
				continue
			}
			ycands := y.cands.Intersect(yseenCands)
			if xcands.IsNull() {
				continue
			}
			for _, xcand := range xcands.GetList() {
				for _, ycand := range ycands.GetList() {
					if xcand == ycand {
						continue
					}
					impossibles[[2]int{xcand, ycand}] = als
				}
			}
		}
		if ok, rs := verifyAlignedPair(x, y, impossibles); ok {
			used = ok
			results = append(results, rs...)
		}
		switchImpossibles := map[[2]int][]*Cell{}
		for k, v := range impossibles {
			switchImpossibles[[2]int{k[1], k[0]}] = v
		}
		if ok, rs := verifyAlignedPair(y, x, switchImpossibles); ok {
			used = ok
			results = append(results, rs...)
		}
	}
	return
}

func verifyAlignedPair(xCell *Cell, yCell *Cell, impossibles map[[2]int][]*Cell) (ok bool, results []*Result) {
	for _, x := range xCell.cands.GetList() {
		if yCell.cands.Has(x) && !isSameHouse([]*Cell{xCell, yCell}) {
			continue
		}
		rm := true
		xCand := set.NewSet(x)
		result := new(Result)
		result.addHelpCells([]*Cell{xCell, yCell})
		allCells := []*Cell{yCell}
		for _, y := range yCell.cands.GetList() {
			if x == y {
				continue
			}
			yCand := set.NewSet(y)
			if als, ok := impossibles[[2]int{x, y}]; ok {
				result.addHelpCells(als)
				g1 := result.getHelpCandsGroup()
				g1.add(yCand, append(als, yCell))
				allCells = append(allCells, als...)
			} else {
				rm = false
			}
		}
		if rm {
			ok = true
			result.addRemoved(xCand, []*Cell{xCell})
			g2 := result.getHelpCandsGroup()
			g2.add(xCand, allCells)
			result.setStrategyName("Aligned Pair Exclusion")
			results = append(results, result)
		}
	}
	return
}

func getAllALSHouse(house House) (all [][]*Cell) {
	unSolvedNums := []int{}
	for i, cell := range house.cells {
		if !cell.solved() {
			unSolvedNums = append(unSolvedNums, i)
		}
	}
	nakedSets := []set.Set{}
	for size := 2; size < len(unSolvedNums)-2; size++ {
		for _, nakedNums := range combine(unSolvedNums, size) {
			cands := house.cells[nakedNums[0]].cands
			for _, i := range nakedNums[1:] {
				cands = cands.Union(house.cells[i].cands)
			}
			if cands.Count() == size {
				nakedSets = append(nakedSets, set.NewSet(nakedNums...))
			}
		}
	}
	//fmt.Println("nakedSets", nakedSets)
	for size := 1; size < len(unSolvedNums); size++ {
		for _, alsNums := range combine(unSolvedNums, size) {
			cands := house.cells[alsNums[0]].cands
			for _, i := range alsNums[1:] {
				cands = cands.Union(house.cells[i].cands)
			}
			if cands.Count() == size+1 {
				alsSet := set.NewSet(alsNums...)
				ok := true
				for _, nakedSet := range nakedSets {
					if alsSet.Intersect(nakedSet) == nakedSet {
						ok = false
						break
					}
				}
				if ok {
					als := []*Cell{}
					for _, i := range alsNums {
						als = append(als, house.cells[i])
					}
					all = append(all, als)
				}
			}
		}
	}

	return all
}

/*
	func getAllALSHouse(house House) [][]*Cell {
		all := [][]*Cell{}
		unsolvedCells := getUnsolvedCells(house.cells[:])
		n := len(unsolvedCells)
		for i := n - 1; i >= 1; i-- {
			for _, alsa := range combine(unsolvedCells, i) {
				cands := alsa[0].cands
				for _, cell := range alsa[1:] {
					cands = cands.Union(cell.cands)
				}
				if cands.Count() != i+1 {
					continue
				}
				all = append(all, alsa)
			}
		}
		return all
	}
*/
func (g *Grid) getAllALS() [][]*Cell {
	all := [][]*Cell{}
	for _, row := range g.rows {
		all = append(all, getAllALSHouse(row)...)
	}
	for _, col := range g.cols {
		all = append(all, getAllALSHouse(col)...)
	}
	for _, box := range g.boxes {
		all = append(all, getAllALSHouse(box)...)
	}
	slices.SortFunc(all, compareCells)
	all = slices.CompactFunc(all, func(a, b []*Cell) bool { return compareCells(a, b) == 0 })
	return all
}

func compareCells(as, bs []*Cell) int {
	sas := []byte{}
	sbs := []byte{}
	for _, a := range as {
		sas = append(sas, byte(a.rowNum), byte(a.colNum))
	}
	for _, b := range bs {
		sbs = append(sbs, byte(b.rowNum), byte(b.colNum))
	}
	if string(sas) == string(sbs) {
		return 0
	} else if string(sas) > string(sbs) {
		return 1
	} else {
		return -1
	}
}
